package com.freestyle.multidatasource;

import com.freestyle.common.spring.utils.MybatisUtil;
import com.freestyle.common.tkmybatis.MyMapper;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.function.Function;

/**用代码进行数据源切换并执行事务操作
 * Created by rocklee on 2021/2/6 10:20
 */
public class MultiDataSourceUtil <T>{
  public <T> T batchExecute(MyMapper mapper,DataSource dataSource, Function<SqlSession,T> function){
    SqlSession ss=getSqlSession(MybatisUtil.getSqlSessionFromMapperInstance(mapper).getConfiguration(),
            dataSource);
    /***
     * 因为SqlSession的transaction只是移花接木,Spring容器中并没有针对于指定dataSource的TransactionManager,所以这个transaction是无效的
     * (org.mybatis.spring.transaction.SpringManagedTransaction#isConnectionTransactional=false),因为这个SqlSession全程使用Connection来控制事务
     */
    try{
      ss.getConnection().setAutoCommit(false);
      return function.apply(ss);
    }
    catch (Exception e){
      try {
        ss.getConnection().rollback();
      } catch (SQLException throwables) {
        throw new RuntimeException(throwables) ;
      }
      throw new RuntimeException(e) ;
    }
    finally {
      try {
        ss.getConnection().commit();
        ss.getConnection().close();
      } catch (SQLException throwables) {
        throw new RuntimeException(throwables) ;
      }
      ss.close();
    }
  }
  public static SqlSession getSqlSession(Configuration configuration, DataSource dataSource){
    Transaction tx = null;
    try {
      /*
      没用,即使构建一个新的Configuration也没能用上Spring的事务管理器,只能利用database自带的事务管理
      SpringManagedTransactionFactory stf=new SpringManagedTransactionFactory();
      Environment env=new Environment(configuration.getEnvironment().getId(),stf,dataSource);
      Configuration configuration1= (Configuration) BeanUtils.cloneBean(configuration);
      for (Class<?> o:configuration.getMapperRegistry().getMappers()) {
        configuration1.getMapperRegistry().addMapper(o);
      }
      Map<String, MappedStatement> mappedStatements=BeanUtil.getFieldValue(configuration1,"mappedStatements");
      mappedStatements.putAll(BeanUtil.getFieldValue(configuration,"mappedStatements"));
      //configuration1.getMappedStatements().addAll(configuration.getMappedStatements());
      Map<String, ResultMap>resultMaps=BeanUtil.getFieldValue(configuration1,"resultMaps");
      resultMaps.putAll(BeanUtil.getFieldValue(configuration,"resultMaps"));
      //configuration1.getResultMaps().addAll(configuration.getResultMaps());
      //configuration1.getParameterMaps().addAll(configuration.getParameterMaps());
      Map<String, ParameterMap> parameterMaps=BeanUtil.getFieldValue(configuration1,"parameterMaps");
      parameterMaps.putAll(BeanUtil.getFieldValue(configuration1,"parameterMaps"));
      Map<String, KeyGenerator> keyGenerators=BeanUtil.getFieldValue(configuration1,"keyGenerators");
      keyGenerators.putAll(BeanUtil.getFieldValue(configuration,"keyGenerators"));
      //configuration1.getKeyGenerators().addAll(configuration.getKeyGenerators());
      Set<String> loadedResources= BeanUtil.getFieldValue(configuration1,"loadedResources");
      loadedResources.addAll(BeanUtil.getFieldValue(configuration,"loadedResources"));
      configuration1.getSqlFragments().putAll(configuration.getSqlFragments());
      configuration1.getIncompleteMethods().clear();
      configuration1.setEnvironment(env);*/
      final TransactionFactory transactionFactory = configuration.getEnvironment()==null?new ManagedTransactionFactory(): configuration.getEnvironment().getTransactionFactory();
      tx = transactionFactory.newTransaction(dataSource, null, false);
      final Executor executor = configuration.newExecutor(tx, ExecutorType.SIMPLE);
      return new DefaultSqlSession(configuration, executor, false);
    } catch (Exception e) {
      if (tx!=null) {
        try {
          tx.close(); // may have fetched a connection so lets call close()
        } catch (SQLException throwables) {
          throwables.printStackTrace();
        }
      }
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

}
